#include "../include/PageCache.h"

PageCache PageCache::_sInstPageCache;

//获取一个k页的span
Span *PageCache::NewSpan(size_t k)
{
	assert(k > 0);
	//如果比128页大，则直接向堆要
	if (k > NPAGES - 1)
	{
		void *ptr = SystemAlloc(k);
		// Span* span = new Span;

		Span *span = _spanPool.New();
		span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
		span->_n = k;
		//_idSpanMap[span->_pageId] = span;

		_idSpanMap.set(span->_pageId, span);
		return span;
	}

	if (!_spanLists[k].Empty())
	{
		Span *kSpan = _spanLists[k].PopFront();

		//建立页号与span的映射，方便central cache回收小块内存时查找对应的span
		for (PAGE_ID i = 0; i < kSpan->_n; i++)
		{
			//_idSpanMap[kSpan->_pageId + i] = kSpan;

			_idSpanMap.set(kSpan->_pageId + i, kSpan);
		}

		return kSpan;
	}
	else
	{
		//桶里是空的，检查后面的桶有没有span，如果有可以拿过来切小一点
		// k页返回给CentralCache，剩下的i-k页挂到对应的桶位置
		for (size_t i = k + 1; i < NPAGES; ++i)
		{
			//找到了后面不为空的桶
			if (!_spanLists[i].Empty())
			{
				Span *nSpan = _spanLists[i].PopFront();
				// Span* kSpan = new Span;
				Span *kSpan = _spanPool.New();

				//在nSpan的头部切一个k页下来
				//剩下的挂到桶的对应位置
				kSpan->_pageId = nSpan->_pageId;
				kSpan->_n = k;
				nSpan->_pageId += k;
				nSpan->_n -= k;

				_spanLists[nSpan->_n].PushFront(nSpan);

				//存储nSpan的首位页号根nSpan映射，方便PageCache回收内存时进行合并查找
				//因为合并时只需要首和尾即可
				//_idSpanMap[nSpan->_pageId] = nSpan;
				//_idSpanMap[nSpan->_pageId+nSpan->_n-1] = nSpan;

				_idSpanMap.set(nSpan->_pageId, nSpan);
				_idSpanMap.set(nSpan->_pageId + nSpan->_n - 1, nSpan);

				//建立id和span的映射关系，方便CentralCache回收小块时
				//可以通过id找到span
				for (PAGE_ID i = 0; i < kSpan->_n; ++i)
				{
					//_idSpanMap[kSpan->_pageId + i] = kSpan;
					_idSpanMap.set(kSpan->_pageId + i, kSpan);
				}
				return kSpan;
			}
		}
		//走到这里，说明后面没有比k大的span
		//去找堆要一个128页的span
		// Span* bigSpan = new Span;
		Span *bigSpan = _spanPool.New();

		void *ptr = SystemAlloc(NPAGES - 1);

		bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
		bigSpan->_n = NPAGES - 1;

		_spanLists[bigSpan->_n].PushFront(bigSpan);
		//要完128页的span后再递归调用自己
		return NewSpan(k);
	}
}

Span *PageCache::MapObjectToSpan(void *obj)
{
	//算obj的页号
	PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT;
	////在访问map前，要加锁，因为map不保证线程安全
	// std::unique_lock<std::mutex>lock(_pageMtx);

	// auto ret = _idSpanMap.find(id);
	// if (ret != _idSpanMap.end())
	//{
	//	return ret->second;
	// }
	// else
	//{
	//	assert(false);
	//	return nullptr;
	// }

	auto ret = (Span *)_idSpanMap.get(id);
	assert(ret != nullptr);

	return ret;
}

//释放空闲span回到Pagecache，合并相邻的span
void PageCache::ReleaseSpanToPageCache(Span *span)
{
	//大于128页的page直接释放给堆
	if (span->_n > NPAGES - 1)
	{
		void *ptr = (void *)(span->_pageId << PAGE_SHIFT);
		SystemFree(ptr);
		// delete span;
		_spanPool.Delete(span);
		return;
	}
	//对span前后的页，进行合并，来缓解外碎片的问题
	while (1)
	{
		PAGE_ID prevId = span->_pageId - 1;
		////找前面的页号，是否在PageCache中
		// auto pret = _idSpanMap.find(prevId);
		// if (pret == _idSpanMap.end())
		//{
		//	//不在，直接跳出循环
		//	break;
		// }

		auto ret = (Span *)_idSpanMap.get(prevId);
		if (ret == nullptr)
		{
			break;
		}

		//前面的页号在使用，不合并
		// Span* prevSpan = pret->second;
		Span *prevSpan = ret;

		if (prevSpan->_isUse == true)
		{
			break;
		}
		//合并出超过128页的span没办法管理，也不合并
		if (prevSpan->_n + span->_n > NPAGES - 1)
		{
			break;
		}
		span->_pageId = prevSpan->_pageId;
		span->_n += prevSpan->_n;

		_spanLists[prevSpan->_n].Erase(prevSpan);
		// delete掉它，因为在NewSpan这个函数中我们new了一个对象
		// delete prevSpan;
		_spanPool.Delete(prevSpan);
	}
	//对span前后的页，进行合并，来缓解外碎片的问题
	while (1)
	{
		PAGE_ID nextId = span->_pageId + span->_n;
		////找后面的页号，是否在PageCache中
		// auto nret = _idSpanMap.find(nextId);
		// if (nret == _idSpanMap.end())
		//{
		//	//不在，直接跳出循环
		//	break;
		// }
		// Span* nextSpan = nret->second;

		auto ret = (Span *)_idSpanMap.get(nextId);
		if (ret == nullptr)
		{
			break;
		}

		//后面的页号在使用，不合并
		// Span* nextSpan = pret->second;
		Span *nextSpan = ret;
		//后面的页号在使用，不合并
		if (nextSpan->_isUse == true)
		{
			break;
		}
		//合并出超过128页的span没办法管理，也不合并
		if (nextSpan->_n + span->_n > NPAGES - 1)
		{
			break;
		}
		span->_n += nextSpan->_n;

		_spanLists[nextSpan->_n].Erase(nextSpan);
		// delete nextSpan;
		_spanPool.Delete(nextSpan);
	}
	_spanLists[span->_n].PushFront(span);
	span->_isUse = false;
	//_idSpanMap[span->_pageId] = span;
	//_idSpanMap[span->_pageId+span->_n-1] = span;
	_idSpanMap.set(span->_pageId, span);
	_idSpanMap.set(span->_pageId + span->_n - 1, span);
}